﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DDD;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;


namespace DDD {
    /// <summary>
    /// <see cref="UniformArray"/>クラスに拡張メソッドを追加するクラス
    /// </summary>
    public static class UniformArrayExtensions {
        /// <summary>
        /// レンダリング
        /// </summary>
        /// <remarks>
        /// Uniform変数は毎回レンダリングする度に全てデータを再取得してGPUに転送します｡
        /// 現在の所転送が必要な変数だけ転送するように修正する予定はありません｡
        /// (プロパティの値が変更されたかどうかをチェックするのはかなり面倒)
        /// ただしもう１クッション(Reflesh()関数)を置く可能性はあります。
        /// (*1) テクスチャーユニットの番号を取得するのにここでAppearance.GetSamplerIndex()を使っているが、
        /// これは事前に解決されていないと無駄にややこしい。
        /// </remarks>
        /// <param name="uarry"><see cref="UniformArray"/>オブジェクト</param>
        /// <param name="prog">カレントのシェーダー</param>
        /// <param name="node">カレントのノード</param>
        /// <param name="app">カレントのアピアランス</param>
        public static void Render (this UniformArray uarry, ShaderProgram prog, Node node, Appearance app) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[prog];
            if (id == 0) {
                throw new InvalidOperationException ("OpenGL id was not Found");
            }
            var cam = node.Root.ActiveCamera.As<Camera> ();
            var mat = app.Material;
            var decs = prog.Uniforms;

            foreach (var unif in uarry) {
                var query = (from x in prog.Uniforms
                             where x.Name == unif.Name
                             select new { Location = x.Location, Index = x.Index}).FirstOrDefault ();
                if (query == null) {
                    // 見つからなかったら無視
                    continue;
                }
                var index = query.Index;
                var loc = query.Location;

                // 実行時ソースオブジェクトの解決
                var src = (unif.Source == UniformArray.CurrentNode) ? node :
                          (unif.Source == UniformArray.CurrentActiveCamera) ? cam :
                          (unif.Source == UniformArray.CurrentMaterial) ? mat : unif.Source;
                var value = (object)null;

                // (1) 即値指定
                if (unif.PropertyName == null) {
                    value = src;
                }
                // (2) プロパティ指定
                if (unif.PropertyName != null) {
                    var propInfo = src.GetType ().GetProperty (unif.PropertyName);
                    if (propInfo == null) {
                        throw new InvalidOperationException ("Can't find property=" + unif.PropertyName);
                    }
                    value = propInfo.GetValue (src, null);
                }
                // (3) サンプラーの変換    (*1)を参照
                if (value.GetType () == typeof (Sampler)) {
                    value = app.GetSamplerIndex ((Sampler)value);
                }
                if (value.GetType () == typeof (Sampler[])) {
                    value = (from s in value as Sampler[]
                             select app.GetSamplerIndex (s)).ToArray ();
                }

                int count;
                ActiveUniformType type;
                var n = GL.GetActiveUniform (id, index, out count, out type);

                if (type == ActiveUniformType.FloatMat2) {
                    var data = ForceArray<float> (value);
                    count = Math.Min (count, data.Length / 4);
                    GL.UniformMatrix2 (loc, count, true, data);
                    continue;
                }
                if (type == ActiveUniformType.FloatMat3) {
                    var data = ForceArray<float> (value);
                    count = Math.Min (count, data.Length / 9);
                    GL.UniformMatrix3 (loc, count, true, data);
                    continue;
                }
                if (type == ActiveUniformType.FloatMat4) {
                    var data = ForceArray<float> (value);
                    count = Math.Min (count, data.Length / 16);
                    GL.UniformMatrix4 (loc, count, true, data);
                    continue;
                }
                if (type == ActiveUniformType.FloatMat3x4) {
                    var data = ForceArray<float> (value);
                    count = Math.Min (count, data.Length / 12);
                    GL.UniformMatrix3x4 (loc, count, true, data);
                    continue;
                }
                if (type == ActiveUniformType.Float) {
                    var data = ForceArray<float> (value);
                    count = Math.Min (count, data.Length / 1);
                    GL.Uniform1 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.FloatVec2) {
                    var data = ForceArray<float> (value);
                    count = Math.Min (count, data.Length / 2);
                    GL.Uniform2 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.FloatVec3) {
                    var data = ForceArray<float> (value);
                    count = Math.Min (count, data.Length / 3);
                    GL.Uniform3 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.FloatVec4) {
                    var data = ForceArray<float> (value);
                    count = Math.Min (count, data.Length / 4);
                    GL.Uniform4 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.Bool) {
                    var data = ForceArray<bool> (value).Select (x => (x == true) ? 1 : 0).ToArray ();
                    count = Math.Min (count, data.Length / 1);
                    GL.Uniform1 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.BoolVec2) {
                    var data = ForceArray<bool> (value).Select (x => (x == true) ? 1 : 0).ToArray ();
                    count = Math.Min (count, data.Length / 2);
                    GL.Uniform2 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.BoolVec3) {
                    var data = ForceArray<bool> (value).Select (x => (x == true) ? 1 : 0).ToArray ();
                    count = Math.Min (count, data.Length / 3);
                    GL.Uniform3 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.BoolVec4) {
                    var data = ForceArray<bool> (value).Select (x => (x == true) ? 1 : 0).ToArray ();
                    count = Math.Min (count, data.Length / 4);
                    GL.Uniform4 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.Int) {
                    var data = ForceArray<int> (value);
                    count = Math.Min (count, data.Length / 1);
                    GL.Uniform1 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.IntVec2) {
                    var data = ForceArray<int> (value);
                    count = Math.Min (count, data.Length / 2);
                    GL.Uniform2 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.IntVec3) {
                    var data = ForceArray<int> (value);
                    count = Math.Min (count, data.Length / 3);
                    GL.Uniform3 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.IntVec4) {
                    var data = ForceArray<int> (value);
                    count = Math.Min (count, data.Length / 4);
                    GL.Uniform4 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.UnsignedInt) {
                    var data = ForceArray<uint> (value);
                    count = Math.Min (count, data.Length / 1);
                    GL.Uniform1 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.UnsignedIntVec2) {
                    var data = ForceArray<uint> (value);
                    count = Math.Min (count, data.Length / 2);
                    GL.Uniform2 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.UnsignedIntVec3) {
                    var data = ForceArray<uint> (value);
                    count = Math.Min (count, data.Length / 3);
                    GL.Uniform3 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.UnsignedIntVec4) {
                    var data = ForceArray<uint> (value);
                    count = Math.Min (count, data.Length / 4);
                    GL.Uniform4 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.Sampler2D ||
                    type == ActiveUniformType.IntSampler2D ||
                    type == ActiveUniformType.UnsignedIntSampler2D) {
                    var data = ForceArray<int> (value);
                    count = Math.Min (count, data.Length / 1);
                    GL.Uniform1 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.SamplerCube) {
                    var data = ForceArray<int> (value);
                    count = Math.Min (count, data.Length / 1);
                    GL.Uniform1 (loc, count, data);
                    continue;
                }
                if (type == ActiveUniformType.Sampler2DShadow) {
                    var data = ForceArray<int> (value);
                    count = Math.Min (count, data.Length / 1);
                    GL.Uniform1 (loc, count, data);
                    continue;
                }

                // ここに載っていないシェーダー型は未対応なので無視
            }
        }

        /// <summary>
        /// プロパティの値を(シェーダーに渡すために)配列に変換します
        /// </summary>
        /// <remarks>
        /// ルールは､
        ///   1. 組み込み型                --> 配列に入れて返す
        ///   2. IExportableを実装している --> 配列に直して返す
        ///   3. (1又は2の)配列 　　       --> 全ての要素を配列に直してフラットな配列にして返す
        /// 配列に直せない型の場合はnullが返ります。
        /// 組み込み型 = Boolean、Byte、SByte、Int16、UInt16、Int32、UInt32、Int64、UInt64、Char、Double、および Single
        /// </remarks>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        private static T[] ForceArray<T> (dynamic obj) {
            if (obj.GetType ().IsPrimitive) {
                return new T[1] { (T)obj };
            }
            if (obj is IExportable<T>) {
                var array = obj as IExportable<T>;
                var tmp = new T[array.ComponentCount];
                for (var i = 0; i < tmp.Length; i++) {
                    tmp[i] = array[i];
                }
                return tmp;
            }
            if (obj.GetType ().IsArray) {
                var array = (Array)obj;
                var tmp = new T[array.Length][];
                for (var i = 0; i < array.Length; i++) {
                    tmp[i] = ForceArray<T> (array.GetValue (i));
                }
                return tmp.SelectMany (x => x).ToArray ();
            }
            return null;
        }

    }
}
